Generic classes in C# and Java can be thought of as class templates, which let us use placeholders instead of certain types. What they don't allow is having generic property names. Typescript does not have such limitation and easily lets us do that:
export type MakeProp<TKey extends string , TType> = {
[key in TKey]: TType
}
// MakeProp<'foo', number>
type equivalent1 = {
foo: number;
}
TS playground ↗
This does not look so impressive, after all, we could have written this type by hand just as easily. What we should know, however, is that we have passed they type-key barrier ( not a real term :-) ) - we managed to make type into a key and create an object shape using only other types. This means, we can use our 'MakeProp' type within other generic types in Typescript. With mapped and conditional types, this means we can construct much more complex types with our mappings.
If you keep track of TypeScript innovations, you've probably used a Mapped Type like Partial, and maybe even pressed “Go to definition” to see, how it is defined under the hood.
type Partial<T> = {
[P in keyof T]?: T[P];
}
Sadly, while many people are using predefined Mapped Types, few have adapted writing their own to utilize the full power of Typescript. Even fewer still realize the full extent of their power in types like the following:
// Readonly that is applied recursively
// to properties which are objects, instead of just first level
type ReadonlyDeep<TType> = {
readonly [key in keyof TType]: TType extends Object
? ReadonlyDeep<TType[key]>
: TType[key];
}
let t = {b: 1, c: {d:2}}
let readonly: Readonly<typeof t> = t;
readonly.c.d = 5; // :-( no error
let readonlyDeep: ReadonlyDeep<typeof t> = t;
readonlyDeep.c.d = 5; // :-) error!
// A way to get keoyf only for certain types of keys
type KeyofMethods<TType> = ({
[key in keyof TType]: TType[key] extends Function
? key // notice, use of key instead of TType[key]
: never
})[keyof TType];
class FooBar {
a: number = 0;
c(){ return 0;};
d(){ return 0;};
}
type km = KeyofMethods<FooBar>; //"c"|"d"
// A way to prohibit access to anything that is not a function,
// to enforce method usage in part of code, while having relaxed rules elsewhere
type MethodsOf<TType> = Pick<TType, KeyofMethods<TType>>
const m = <MethodsOf<FooBar>> new FooBar();
let c: number = m.c(); // fine
let a = m.a; //error!
TS playground ↗
Lets explore the practical application and peek into still more fantastic possibilities.